# [十四] Spring的事务管理 - 隔离级别

导读

MySQL官方对隔离界别的定义:The transaction isolation level of a SQL-transaction defines the degree to which the operations on SQL-data, or schemas in that SQL-transaction are affected by the effects of and can affect operations on SQL-data or schemas in concurrent SQL-transactions.

大概意思就是说:"一个sql事务的事务隔离级别定义的sql数据上的操作,或模式的sql事务受到的影响,会影响操作的sql数据并发sql事务或模式"。简单地说,隔离级别定义了并发事务在修改数据时的交互方式(如何相互影响的)。

Spring 事务中的隔离级别最终实现还是依赖数据库的,是在数据库的基础上进行了业务的包装,以便更好的和数据库结合,本身针对MySQL数据库中的隔离级别进行举例说明。

# 隔离级别的分类

名字 隔离级别 脏读 不可重复读 幻读
读未提交 ISOLATION_READ_UNCOMMITTED
读提交 ISOLATION_READ_COMMITTED 不会
可重复读 ISOLATION_REPEATABLE_READ 不会 不会
串行化 ISOLATION_SERIALIZABLE 不会 不会 不会

首先可以查看MySQL数据库的默认隔离级别是可重复读(RR)

select @@tx_isolation;

+-----------------------+
| @@tx_isolation 		|
+-----------------------+
| REPEATABLE-READ       |
+-----------------------+

# REPEATABLE-READ(可重复读)

RR

可重复读(REPEATABLE-READ):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录(读已经提交的,其实是读早于本事务开始且已经提交的),但是不能看到其他事务对已有记录的更新(即晚于本事务开始的),并且该事务不要求与其他事务是“可串行化”的。

换句话说,就是在可重复读(Repeatable Read)的隔离级别下,其他事务线程添加了id=10的数据并提交,那么当前线程是读取不到的,但是数据其实已经添加到了表中,也就是说如果在当前线程中再执行添加id=10的数据会报错,提示当前id已经存在。

# 示例

  • 首先,线程A执行到select * from cp_goods查询操作,并没有提交
# set transaction isolation level repeatable read;
begin ;
select * from cp_goods;
# commit ;

查询结果是:

1	test	100
2	test1	100
3	test2	100

  • 此时,另外一个线程B由于业务需求要对表cp_goods进行修改,添加一条数据并提交
# set transaction isolation level repeatable read;
begin ;
insert into cp_goods ( name, count) VALUE ( 'test3',100);
commit ;
  • 然后,回到A再执行select * from cp_goods,发现查询结果并没有改变,这就是可重复读。
1	test	100
2	test1	100
3	test2	100

  • 然后,A接着执行commit提交操作,再次执行select * from cp_goods,发现可以查到数据了。
1	test	100
2	test1	100
3	test2	100
4	test3	100

在RR隔离级别下为事务设置了一个一致性读视图(即快照),之后读取数据,就是根据这个快照来获取,这样,就不能看到他晚于本事务的事务对已有记录的更新,也就是为什么线程B提交后,A查询结果仍然可以保持不变。

由此可见,可重复读的隔离级别下使用了MVCC机制,使用的是Next-Key Lock锁算法,select操作不会更新版本号,是快照读(历史版本);insertupdatedelete会更新版本号,是当前读(当前版本)。

知识点

MVCC(多版本并发控制),InnoDB为每行记录添加了一个版本号(系统版本号),每当修改数据时,版本号加一。在读取事务开始时,系统会给事务一个当前版本号,事务会读取版本号<=当前版本号的数据,这时就算另一个事务插入一个数据,并立马提交,新插入这条数据的版本号会比读取事务的版本号高,因此读取事务读的数据还是不会变。

# READ-UNCOMMITTED(读未提交)

提示

最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。

# 示例

  • 首先,线程A执行到select * from cp_goods查询
set transaction isolation level read uncommitted ;
begin ;
select * from cp_goods;
# commit ;

查询结果是:

1	test	100
2	test1	100
3	test2	100
4	test3	100

  • 此时,另外一个线程B由于业务需求要对表cp_goods进行修改,添加并修改一条数据,没有提交
set transaction isolation level read uncommitted ;
begin ;
insert into cp_goods ( name, count) VALUE ( 'test4',100);
update cp_goods  set count= count - 10 where id = 1;
# commit ;
  • 然后,回到A再执行select * from cp_goods,发现查询读取到了B未提交的数据,这就是读未提交。

查询结果是:

1	test	90
2	test1	100
3	test2	100
4	test3	100
5	test4	100

# READ-COMMITTED(读提交)

提示

允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。

# 示例

  • 首先,线程A执行到select * from cp_goods查询
set transaction isolation level read committed ;
begin ;
select * from cp_goods;
# commit ;

查询结果是:

1	test	100
2	test1	100
3	test2	100
4	test3	100

  • 此时,另外一个线程B由于业务需求要对表cp_goods进行修改,添加并修改一条数据,并且提交
set transaction isolation level read committed ;
begin ;
insert into cp_goods ( name, count) VALUE ( 'test5',100);
update cp_goods  set count= count - 10 where id = 2;
commit ;
  • 然后,回到A再执行select * from cp_goods,发现查询读取到了B提交的数据,这就是读提交。

查询结果是:

1	test	90
2	test1	90
3	test2	100
4	test3	100
5	test4	100
6	test5	100

知识点

MySQL常用的两种引擎MyISAMInnoDBMyISAM默认使用表锁,InnoDB默认使用行锁。 使用InnoDB引擎,如果筛选条件里面没有索引字段,就会锁住整张表,否则的话,锁住相应的行。